<?php
interface ss_dao_da_interface{
	
}

class ss_dao_pdo implements ss_dao_da_interface{
	private $connection;
	private $user;
	private $pass;
	private $conParam;
	protected $pdo;
	private $statements=array();
	
	public function __construct($connection,$user=null,$pass=null,$conParam=null){
		$this->connection =$connection;
		$this->user=$user;
		$this->pass=$pass;
		$this->conParam=$conParam;
		//parent::__construct();
	}
	
	public function getQueryBuilder(){
		return new ss_dao_query();
	}
	
	public function exec($sql){
		ss_error::group('Executing query');
		$this->setPdo();
		ss_error::log($sql,'SQL Query');
		$result=$this->pdo->exec($sql);
		ss_error::log($result,'Rows affected');
		ss_error::log($this->pdo->errorInfo(),'Error');
		ss_error::groupEnd();
		return $result;
	}
	
	public function query($sql){
		ss_error::group('Running query');
		$this->setPdo();
		ss_error::log($sql,'SQL Query');
		if(!($result=$this->pdo->query($sql))){
			if($result instanceof PDO){
				ss_error::group('Error during SQL query');
				ss_error::log($result->rowCount(),'Rows affected');
				ss_error::log($result->errorInfo(),'Error');
				if($result->errorCode()=='HY000'){
					$this->newPDO();
					$this->query($sql);
			}
			} else {
				ss_error::group('Error during SQL query');
				ss_error::log($this->pdo->errorInfo(),'Error');
					$this->newPDO();
					$result=$this->query($sql);
			}
			ss_error::groupEnd();
		}
		ss_error::log($result->rowCount(),'Rows affected');
		ss_error::groupEnd();
		return $result;
	}
	
	public function prepare($sql,$opt=array()){
		if(array_key_exists($sql,$this->statements)) return $this->statements[$sql];
		$this->setPdo();
		ss_error::log($sql,'SQL Prepare');
		if(!($result=$this->pdo->prepare($sql,$opt))){
			ss_error::group('Error during SQL preparation');
			ss_error::log($opt,'Options');
			ss_error::log($result->errorInfo(),'Error');
			ss_error::groupEnd();
		}
		return $result;
	}
	
	public function execute($stmt,$values=array()){
		foreach($values as $key=>$val){
			if(is_null($val)){
				$stmt->bindValue($key,$val,PDO::PARAM_NULL);
			} elseif(is_numeric($val)){
				$stmt->bindValue($key,$val,PDO::PARAM_INT);
			} elseif(is_bool($val)) {
				$stmt->bindValue($key,$val,PDO::PARAM_BOOL);
			} else {
				$stmt->bindValue($key,$val);
			}
		}
		if(!($result=$stmt->execute())){
			ss_error::group('Error during SQL execution');
			ss_error::log($values,'Values');
			ss_error::log($stmt->rowCount(),'Rows affected');
			ss_error::log($stmt->errorInfo(),'PDO Error info');
			ss_error::groupEnd();
		}
		return $result;
	}
	
	public function lastInsertId(){
		return $this->pdo->lastInsertId();
	}
	
	public function runStmt($sql,$val=array(),$opt=array()){
		ss_error::group('Running prepared statement');
		//ss_error::log($sql,'SQL');
		//$stmt=$this->prepare($sql,$opt);
		if(!($this->execute($stmt=$this->prepare($sql,$opt),$val))){
			if($stmt->errorCode()=='HY000'){
				$this->newPDO();
				ss_error::group('Retry statemet');
					$this->runStmt($sql,$val,$opt);
				ss_error::groupEnd();
			}
		} else {
			ss_error::log($stmt->rowCount(),'Affected rows');
		}
		ss_error::groupEnd();
		return $stmt;
	}
	
	public static function closeCursor($stmt) {
    	do {
    		$stmt->fetchAll();
			ss_error::log($result=$stmt->nextRowSet(),'Anoher resultset');
    	} while ($result);
		ss_error::log($stmt->closeCursor(),'Closing cursor');
	} 
	
	protected function setPdo(){
		if(!($this->pdo instanceof PDO)) $this->newPDO();
		//ss_error::log($this->pdo->errorCode(),'PDO Error state');
		//$this->pdo=new PDO($this->connection, $this->user, $this->pass,$this->conParam);
		//$this->pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
		//PDO::setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
	}
	
	protected function newPDO(){
		ss_error::log('Creating new PDO');
		$this->pdo=new PDO($this->connection, $this->user, $this->pass,$this->conParam);
		$this->pdo->exec("set names utf8");
	}
	
	public function getMetaArray($stmt){
		$result=array();
		while($row=$stmt->fetch()){
			$result['meta_'.$row['key']]=$row['value'];
		}
		return $result;
	}
}

class ss_dao_trans extends ss_dao_pdo{
	private $state=false;
	
	public function beginTransaction(){
		$this->setPdo();
		$this->pdo->beginTransaction();
	}
	
	public function commit(){
		$this->setPdo();
		$this->pdo->commit();
	}
	
	public function rollback(){
		$this->setPdo();
		$this->pdo->rollback();
	}
}

/*
interface ss_dao_dao_interface{
	
}
class ss_dao_dao implements ss_dao_dao_interface{
	protected $da;
	
	public function __construct(ss_dao_da_interface $da){
		$this->da=$da;
	}
	
	protected function execute($sql,$data=null){
		ss_error::log($sql,'Preparing '.$sql);
		$stm=$this->da->prepare($sql);
		$stm->execute($data);
		return $stm;
	}
}

interface ss_dao_model_interface{
	
}

class ss_dao_model implements ss_dao_model_interface{
	protected $dao;
	
	public function __construct(ss_dao_dao $dao){
		$this->dao=$dao;
	}
	
	public function pdo(){
		return $this->dao->pdo();
	}
}
*/
interface ss_dao_object_interface{
	public function getAttributes();
	public function api_master();
	public function master();
	public function api_detail();
	public function detail();
}
class ss_dao_AtributeNotValidException extends Exception{
	
}

class ss_dao_object implements ss_dao_object_interface{
	protected $modified=array();
	protected $attributes=array();
	protected $meta=array();
	protected $valid;
	protected $primary;
	
	public function __construct($primary=null){
		if(is_array($primary)){
			$this->setAttributes($primary);
		} else {
			$this->primary=$primary;
		}
	}
	
	protected function dateToMysql($date){
		return str_replace(array('T','Z'),' ',$date);
	}
	
	public function primary($val=null){
		if(!is_null($val)) $this->attributes[$this->primary]=$val;
		return $this->attributes[$this->primary];
	}
	/*
	public function __construct($attr=null,$fromSource=false){
		$this->setAttributes($attr,$fromSource);
	}
	*/
	public function __set($attr,$val){
		//ss_error::trace('__set');
		if(is_array($this->valid) && !in_array($attr,$this->valid)) throw new ss_dao_AtributeNotValidException();
		//if(isset($this->attributes[$attr]) && $this->attributes[$attr]!=$val && (!in_array($attr,$this->modified))) $this->modified[]=$attr;
		//$this->attributes[$attr]=$val;
		if((isset($this->attributes[$attr]) && $this->attributes[$attr]!=$val) || (!isset($this->attributes[$attr]))) {
			if(!in_array($attr,$this->modified)) $this->modified[]=$attr;
			$this->attributes[$attr]=$val;
			return $this;
		}
		if((isset($this->meta[$attr]) && $this->meta[$attr]!=$val) || (!isset($this->meta[$attr]))) {
			if(!in_array($attr,$this->modified)) $this->modified[]=$attr;
			$this->meta[$attr]=$val;
			return $this;
		}
		if(strpos($attr,'meta_')===0){
			$this->meta[$attr]=$val;
		}
		return $this;
	}
	
	public function __get($attr){
		if(is_array($this->valid) && !in_array($attr,$this->valid)) throw new ss_dao_AtributeNotValidException();
		if(array_key_exists($attr,$this->attributes)) return $this->attributes[$attr];
		if(array_key_exists($attr,$this->meta)) return $this->meta[$attr];
		return false;
	}
	
	public function __call($func,$args){
		//ss_error::trace('__call');
		if(array_key_exists($func,$this->attributes)||array_key_exists($func,$this->meta)) {
			if(count($args)>0 && !is_null($args[0])) $this->{$func}=$args[0];//$this->attributes[$func]=$args[0]; //
			return $this->{$func};//return $this->attributes[$func];
		}
		throw new BadMethodCallException();
	}
	
	public function getMeta(){
		if(!is_array($this->meta())) ss_error::log('load meta');
		return $this->meta;
	}
	
	public function setMeta($meta){
		$this->meta=$meta;
	}
	
	public function getAttributes(){
		return $this->attributes;
	}
	
	public function api_detail(){
		return $this->getData();
	}
	
	public function detail(){
		return $this->api_detail();
	}
	
	public function api_master(){
		return $this->getData();
	}
	
	public function master(){
		return $this->api_master();
	}
	
	public function getData(){
		$result=array();
		foreach(array_keys($this->attributes) as $key){
			$result[$key]=$this->{$key}();
		}
		return $result;
	}
	
	public function setAttributes($attr,$clean=false){
		if(!is_array($attr)) return $this;
		if(is_array($this->valid) && count(array_diff($attr,$this->valid))>0) throw new ss_dao_AtributeNotValidException();
		$this->attributes=$attr;
		if($clean===false) $this->modified=array_keys($this->attributes);
		return $this;
	}
	
	public function clean(){
		$this->modified=array();
		return $this;
	}
	
	public function changed(){
		return $this->modified;
	}
}

interface ss_dao_mapper_interface{
	public function api_master();
	public function master();
	public function api_detail();
	public function detail();
}

abstract class ss_dao_mapper{
	protected $table;
	protected $primary;
	protected $da;
	protected $injector;
	
	public function __construct(ss_dao_da_interface $da, ss_core_injectorInterface $injector){
		$this->da=$da;
		$this->injector=$injector;
	}
	
	protected function getQueryBuilder(){
		return $this->da->getQueryBuilder();
	}
	
	protected function getObjectString($base){
		if(class_exists('site_'.$base)){
			return 'site_'.$base;
		} else {
			return 'ss_'.$base;
		}
	}
	
	public function setInjector(Phemto $injector){
		$this->injector=$injector;
	}
	
	public function getByAttribute($val,$attr){
		$attr=$attr||$this->primary;
	}
	
	public function insert(ss_dao_object_interface $obj){
		$result=$obj->getAttributes();
		//foreach($obj->changed() as $val) $result[$val]=$obj->{$val};
		ss_error::log($result,'Changed data');
		$q=$this->injector->create('ss_dao_query');
		$sql=$q->from(array(
				$this->table))
			->set(array($result))
			->insert();
		ss_error::log($sql);
		ss_error::log($q->values());
		$stmt=$this->da->runStmt($sql,$q->values());
		//$id=$this->da->lastInsertId();
		$obj->{$this->primary}=$this->da->lastInsertId();
		return $obj;
	}
	
	public function update(ss_dao_object_interface $obj){
		$result=array();
		foreach($obj->changed() as $val) $result[$val]=$obj->{$val};
		ss_error::log($result,'Changed data');
		if(count($result)==0) return false;
		if(!$obj->primary()) return $this->insert($obj);
		$q=$this->injector->create('ss_dao_query');
		$sql=$q->from(array(
			$this->table))
		->set(array($result))
		->where(array(
			array($this->primary=>$obj->{$this->primary}))
			)
		->update();
		ss_error::log($sql);
		ss_error::log($q->values());
		$stmt=$this->da->runStmt($sql,$q->values());
		//return false;
	}
	
	public function changeRelation($table,$del=null,$add=null){
		$q=$this->injector->create('ss_dao_query');
		if($del){
			$sql=$q->from(array($table))
				->where($del)
				->delete();
			ss_error::log($sql);
			$this->da->exec($sql);
		}
		if($add){
			$sql=$q->from(array($table))
				->multiSet($add)->insert();
			ss_error::log($sql);
			$this->da->exec($sql);
		}
	}
	
	public function delete(ss_dao_object_interface $obj){
		return false;
	}
	
	public function master($query=null){
		return $this->api_master($query);
	}
	
	public function detail($id){
		return $this->api_detail($id);
	}
	
	abstract public function api_master($query=null);
	abstract public function api_detail($id);
}

?>